Skip to content

feat: add paginated list decorators for prompts, resources, and tools #1286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

maxisbey
Copy link
Contributor

@maxisbey maxisbey commented Aug 20, 2025

Summary

This PR adds paginated decorator methods for listing prompts, resources, and tools to support cursor-based pagination as defined in the MCP spec.

Design Decision for Discussion

I chose to implement these as new decorator methods (list_prompts_paginated, list_resources_paginated, list_tools_paginated) rather than modifying the existing decorators.

Rationale:

  • Backward compatibility: Modifying the existing decorators to require a cursor parameter in the callback would break existing uses of these decorators.
  • Clean separation: Users can opt-in to pagination support without changing existing code
  • Type safety: The paginated decorators have different signatures (accept cursor, return Result directly)

Alternative considered:

  • We could have modified the existing decorators to accept both signatures, but this would complicate the implementation and reduce type safety
    • Either we could have forced the handlers to take in a cursor, breaking current uses of the SDK
    • Or, we could have done some weird option argument thing with inspecting the callbacks, but this felt a bit too hacky compared to how the current lowlevel server works.

Looking for feedback on whether this approach makes sense or if there's a preferred pattern for handling this migration.

Changes

  • Add list_prompts_paginated() decorator that accepts a cursor parameter
  • Add list_resources_paginated() decorator that accepts a cursor parameter
  • Add list_tools_paginated() decorator that accepts a cursor parameter
  • Add unit tests verifying cursor is correctly passed through to handlers
  • Tool cache is properly maintained in list_tools_paginated

Test plan

  • Unit tests pass for all three paginated decorators
  • Tests verify cursor passthrough for both None and string values
  • Manually test via the inspector and with the example client code

🤖 Generated with Claude Code

👨 Below generated by the human, Max Isbey

Manual testing

I spun up the MCP inspector and ran the example pagination server in both stdio mode and SSE mode. Below is some testing evidence using the SSE mode:

Clicking "List More Resources" correctly lists all 30 resources:

Note: this works for prompts and tools as well

Requesting a resource works:

Running the pagination client example code works listing all 30 resources:

Copy link
Member

@jerome3o-anthropic jerome3o-anthropic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a some docs with examples? not sure if we have good docs for the lowlevel api but some usage examples would be great (if not just to help with reviewing).

Otherwise this looks good to me. happy with the separate functions to avoid breaking changes. keen to know how you think we should approach this in the high level interface.

@maxisbey maxisbey force-pushed the feat/list-pagination branch from 91fa584 to 9d72703 Compare August 20, 2025 14:02
@maxisbey
Copy link
Contributor Author

Can we have a some docs with examples? not sure if we have good docs for the lowlevel api but some usage examples would be great (if not just to help with reviewing).

Otherwise this looks good to me. happy with the separate functions to avoid breaking changes. keen to know how you think we should approach this in the high level interface.

Updated the docs, added example code, and included testing in the PR desription

@maxisbey maxisbey force-pushed the feat/list-pagination branch from 66ee42f to 83d4890 Compare August 20, 2025 15:43
@jerome3o-anthropic jerome3o-anthropic self-requested a review August 20, 2025 15:44
maxisbey and others added 2 commits August 21, 2025 14:50
Add list_prompts_paginated, list_resources_paginated, and list_tools_paginated
decorators to support cursor-based pagination for listing endpoints.

These decorators:
- Accept a cursor parameter (can be None for first page)
- Return the respective ListResult type directly
- Maintain backward compatibility with existing non-paginated decorators
- Update tool cache for list_tools_paginated

Also includes simplified unit tests that verify cursor passthrough.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Apply automatic formatting from ruff to ensure code meets project standards:
- Remove trailing whitespace
- Adjust line breaks for consistency
- Format function arguments according to line length limits
@maxisbey maxisbey force-pushed the feat/list-pagination branch from 550e3fe to 70435e5 Compare August 21, 2025 13:50
- Create mcp_simple_pagination example server demonstrating all three paginated endpoints
- Add pagination snippets for both server and client implementations
- Update README to use snippet-source pattern for pagination examples
- Move mutually exclusive note to blockquote format for better visibility
- Complete example shows tools, resources, and prompts pagination with different page sizes
@maxisbey maxisbey force-pushed the feat/list-pagination branch from 70435e5 to 5d823c8 Compare August 21, 2025 13:51
@jerome3o-anthropic jerome3o-anthropic self-requested a review August 21, 2025 14:02
@maxisbey maxisbey enabled auto-merge (squash) August 21, 2025 14:02
@maxisbey maxisbey requested a review from pcarleton August 21, 2025 14:17
@@ -25,22 +25,22 @@ repos:
hooks:
- id: ruff-format
name: Ruff Format
entry: uv run ruff
entry: uv run --frozen ruff
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for below

Suggested change
entry: uv run --frozen ruff
entry: ruff

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, updated

Comment on lines +245 to +257
def list_prompts_paginated(self):
def decorator(func: Callable[[types.Cursor | None], Awaitable[types.ListPromptsResult]]):
logger.debug("Registering handler for PromptListRequest with pagination")

async def handler(req: types.ListPromptsRequest):
result = await func(req.params.cursor if req.params else None)
return types.ServerResult(result)

self.request_handlers[types.ListPromptsRequest] = handler
return func

return decorator

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't the list_prompts handle pagination?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well there were a few paths and I wanted to just put something down to start the discussion.

There were three options I was thinking:

  1. Separate decorators (current state of this PR):
    • Backwards compatible, it won't break existing uses of list_prompts where it's used on a function that takes no arguments
    • Makes implementation of this easier, rather than having to do inspection on the func being passed in to see whether it takes an argument or not, it's just separated into two methods
  2. Single decorator but break backwards compatibility:
    • This would just be changing list_prompts to look exactly like list_prompts_paginated, all funcs passed in need to take in an argument of types.Cursor | None
    • Breaks potentially a lot of existing uses of the lowlevel code
  3. Single decorator, union type:
    • This would be having a single list_prompts that takes in a func that looks like Callable[[types.Cursor | None], Awaitable[types.ListPromptsResult]] | Callable[[], Awaitable[list[types.Prompt]]]
    • Makes it backwards compatible while allowing for an optional cursor
    • Makes implementation a bit trickier by having to do inspection on the function to make sure it fits the shapes expected.

Were you preferring option 2 or 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably leaning towards option 3 now though :)

Copy link
Member

@Kludex Kludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backward compatibility: Modifying the existing decorators to require a cursor parameter in the callback would break existing uses of these decorators.

You can make it optional, can't you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants